package com.bingxue.edu.management.education.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.json.JSONObject;
import com.bingxue.edu.common.enums.OperateStatusEnum;
import com.bingxue.edu.common.enums.PayTypeEnum;
import com.bingxue.edu.common.enums.StudentClassEnum;
import com.bingxue.edu.framework.common.constant.CacheConstants;
import com.bingxue.edu.framework.common.model.PageQuery;
import com.bingxue.edu.framework.common.util.ValidationUtils;
import com.bingxue.edu.framework.exception.BusinessException;
import com.bingxue.edu.management.education.model.entity.ClassInfo;
import com.bingxue.edu.management.education.model.entity.EnrollChangeRecord;
import com.bingxue.edu.management.education.model.entity.Enrollment;
import com.bingxue.edu.management.education.model.entity.Lesson;
import com.bingxue.edu.management.education.model.entity.LessonStudent;
import com.bingxue.edu.management.education.model.query.ClassCancelQuery;
import com.bingxue.edu.management.education.model.req.ClassCancelReq;
import com.bingxue.edu.management.education.model.req.ClassCancelVerifyReq;
import com.bingxue.edu.management.education.model.resp.ClassCancelDetailResp;
import com.bingxue.edu.management.education.model.resp.ClassCancelResp;
import com.bingxue.edu.management.shop.mapper.FinAccountMapper;
import com.bingxue.edu.management.shop.mapper.OrderMapper;
import com.bingxue.edu.management.shop.model.entity.FinAccount;
import com.bingxue.edu.management.shop.model.entity.Order;
import com.bingxue.edu.management.shop.model.entity.OrderItem;
import com.bingxue.edu.management.shop.model.entity.Refund;
import com.bingxue.edu.management.shop.model.req.FuiouRefundOrderCreateReq;
import com.bingxue.edu.management.shop.service.FuiouPayService;
import com.bingxue.edu.management.shop.service.OrderItemService;
import com.bingxue.edu.management.shop.service.RefundService;
import com.bingxue.edu.management.shop.service.SellRecordService;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.bingxue.edu.common.enums.ChangeTypeEnum.CANCEL_CLASS;
import static com.bingxue.edu.common.enums.ChangeTypeEnum.CANCEL_LESSON;
import static com.bingxue.edu.common.enums.OperateSourceEnum.PARENT;
import static com.bingxue.edu.common.enums.OperateStatusEnum.CANCELED;
import static com.bingxue.edu.common.enums.OperateStatusEnum.SUCCESS;
import static com.bingxue.edu.management.education.model.entity.table.ClassInfoTableDef.CLASS_INFO;
import static com.bingxue.edu.management.education.model.entity.table.EnrollChangeRecordTableDef.ENROLL_CHANGE_RECORD;
import static com.bingxue.edu.management.education.model.entity.table.StudentTableDef.STUDENT;
import static com.bingxue.edu.management.shop.model.entity.table.OrderItemTableDef.ORDER_ITEM;

/**
 * 退班退课实现类
 *
 * @author panbo
 * @since 2024/11/16
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ClassCancelService {

    private final OrderMapper orderMapper;
    private final FinAccountMapper finAccountMapper;

    private final EnrollmentService enrollmentService;
    private final ClassInfoService classInfoService;
    private final LessonService lessonService;
    private final LessonStudentService lessonStudentService;
    private final RefundService refundService;
    private final OrderItemService orderItemService;
    private final EnrollChangeRecordService enrollChangeRecordService;
    private final FuiouPayService fuiouPayService;
    private final SellRecordService sellRecordService;

    private final RedissonClient redissonClient;

    /**
     * 申请退班退课
     * 只创建记录，状态设置为审批中，等退费完成后再变更状态。
     */
    @Transactional(rollbackFor = Exception.class)
    public void createClassCancelRecord(ClassCancelReq req) {
        validateCancelReq(req);
        ClassInfo classInfo = classInfoService.getById(req.getClassId());
        ValidationUtils.throwIfNull(classInfo, "班级不存在");
        Enrollment enrollment = enrollmentService.getStudentEnrollment(req.getClassId(), req.getStudentId());
        ValidationUtils.throwIfNull(enrollment, "未找到报名记录");
        OrderItem orderItem = orderItemService.getById(req.getOrderItemId());
        ValidationUtils.throwIfNull(orderItem, "未找到订单记录");
        boolean exist = enrollChangeRecordService.existPendingCancelRecord(enrollment.getId());
        ValidationUtils.throwIf(exist,"存在未完成的退课单,不能重复申请");
        cancelLessonOrClass(req, enrollment, classInfo, orderItem);

        log.info("申请退班退课成功， req: {}", req);
    }

    /**
     * 申请退班
     */
    public void cancelLessonOrClass(ClassCancelReq req, Enrollment enrollment, ClassInfo classInfo, OrderItem orderItem) {
        // 退课课次
        List<LessonStudent> lessonStudentList = getCancelLessonList(req, enrollment);

        Set<String> orderItemIdSet = lessonStudentList.stream().map(LessonStudent::getOrderItemId).collect(Collectors.toSet());
        ValidationUtils.throwIf(orderItemIdSet.size() > 1, "不支持跨订单退费");
        // 创建退款单
        Refund refund = refundService.initNewRefund(req, orderItem, enrollment.getId(), lessonStudentList);
        refundService.save(refund);
        // 创建变更记录
        EnrollChangeRecord enrollChangeRecord = enrollChangeRecordService.initCancelRecord(req, enrollment, classInfo, refund.getCancelLessonIndexes());
        enrollChangeRecord.setBusinessId(refund.getId());
        // 保存报名信息变更记录，状态为审核中
        enrollChangeRecordService.save(enrollChangeRecord);
        // TODO: last lesson class id
    }

    /**
     * 拒绝退班申请
     */
    public void rejectClassCancel(ClassCancelVerifyReq req) {
        EnrollChangeRecord record = enrollChangeRecordService.getById(req.getRecordId());
        ValidationUtils.throwIfNull(record, "退课记录不存在");
        ValidationUtils.throwIf(record.getOperateStatus() != OperateStatusEnum.VERIFYING, "仅支持操作审核中的记录");

        Refund refund = refundService.getById(record.getBusinessId());
        ValidationUtils.throwIfNull(refund, "退费记录不存在");
        ValidationUtils.throwIf(refund.getRefundOff(), "已完成退费");

        record.setOperateStatus(CANCELED);
        enrollChangeRecordService.updateById(record);
//        refundService.updateById(refund);

        log.info("拒绝退课订单审批, {}", req);
    }

    /**
     * 通过退班/退课申请
     * 当退款完成或者工作人员审批后触发，让退班记录生效
     */
    @Transactional(rollbackFor = Exception.class)
    public void passClassCancel(ClassCancelVerifyReq req) {
        ValidationUtils.throwIfNull(req.getRefundType(), "退费方式不能为空");
        ValidationUtils.throwIf(req.getRefundType() == PayTypeEnum.OFFLINE && req.getFinAccountId() == null, "退费账户不能为空");
        // 校验状态
        RLock lock = redissonClient.getLock(CacheConstants.ORDER_REFUND_LOCK_KEY + req.getRecordId());
        try {
            lock.lock(30, TimeUnit.SECONDS);
            EnrollChangeRecord record = enrollChangeRecordService.getById(req.getRecordId());
            ValidationUtils.throwIfNull(record, "退课记录不存在");
            ValidationUtils.throwIf(record.getOperateStatus() != OperateStatusEnum.VERIFYING, "仅支持操作审核中的记录");

            Refund refund = refundService.getById(record.getBusinessId());
            ValidationUtils.throwIfNull(refund, "退费记录不存在");
            ValidationUtils.throwIf(refund.getRefundOff(), "已完成退费");

            // 查询订单信息
            Order order = orderMapper.selectOneById(refund.getOrderId());
            if (order.getPayType() == PayTypeEnum.OFFLINE && req.getRefundType() == PayTypeEnum.FUIOU) {
                throw new BusinessException("线下订单不支持线上退费");
            }

            refund.setRefundType(req.getRefundType());
            refund.setFinAccountId(req.getFinAccountId());
            if (req.getRefundType().equals(PayTypeEnum.FUIOU)){
                refund.setFinAccountId(PayTypeEnum.FUIOU.getValue());
            }

            if (refund.getRefundType() == PayTypeEnum.FUIOU) {
                FuiouRefundOrderCreateReq refundOrderCreateReq = new FuiouRefundOrderCreateReq();
                refundOrderCreateReq.setPayOrderId(order.getId());
                refundOrderCreateReq.setPayOrderDate(order.getCreateTime());
                refundOrderCreateReq.setRefundOrderId(refund.getId());
                refundOrderCreateReq.setRefundOrderDate(LocalDateTime.now());
                refundOrderCreateReq.setRefundAmount(refund.getRefundPrice());
                JSONObject refundOrderResult = fuiouPayService.createRefundOrder(refundOrderCreateReq);
                refund.setOutTradeNo(refundOrderResult.getStr("refund_fas_ssn"));
                String refundSt = refundOrderResult.getStr("refund_st");
                switch (refundSt) {
                    case "5", "6" -> {
                        // 完成退费
                        log.info("线上退费完成，recordId: {}", req);
                        completeClassCancel(record, refund);
                        refund.setRefundOff(true);
                        refund.setRefundTime(LocalDateTime.now());
                    }
                    case "7" -> {
                        log.warn("线上退费失败，req: {}, fuiou response: {}", req, refundOrderResult);
                        record.setOperateStatus(OperateStatusEnum.FAILED);
                        enrollChangeRecordService.updateById(record);
                    }
                    case "8" -> {
                        log.warn("线上退费未完成，退款中，req: {}, fuiou response: {}", req, refundOrderResult);
                        record.setOperateStatus(OperateStatusEnum.REFUNDING);
                        enrollChangeRecordService.updateById(record);
                    }
                }
            } else if (refund.getRefundType() == PayTypeEnum.OFFLINE) {
                // 线下退费，自动完成退课操作
                log.info("线下退费完成，recordId: {}", req);
                completeClassCancel(record, refund);
                refund.setRefundOff(true);
                refund.setRefundTime(LocalDateTime.now());
            }
            refundService.updateRefund(refund);
            log.info("通过退课订单审批, {}", req);
        } catch (Exception e) {
            log.warn("退费失败: {}", e.getMessage());
            throw e;
        } finally {
            lock.unlock();
        }
    }


    /**
     * 通过退班/退课申请
     * 当退款完成或者工作人员审批后触发，让退班记录生效
     */
    public void completeClassCancel(EnrollChangeRecord record, Refund refund) {
        // 获取报名以及报名记录信息
        Enrollment enrollment = enrollmentService.getById(refund.getEnrollmentId());
        ValidationUtils.throwIfNull(enrollment, "报名记录不存在");

        List<LessonStudent> cancelLessonStudentList = lessonStudentService.getListByLessonIndex(enrollment.getId(), refund.getCancelLessonIndexes());
        if (record.getChangeType() == CANCEL_LESSON) {
            // 退课
            lessonStudentService.updateStudentClassStatus(cancelLessonStudentList, StudentClassEnum.CANCEL_LESSON);
        } else if (record.getChangeType() == CANCEL_CLASS) {
            // 退班
            classInfoService.removeStudentFromClass(refund.getStudentId(), refund.getClassId(), StudentClassEnum.CANCEL_CLASS);
            lessonStudentService.updateStudentClassStatus(cancelLessonStudentList, StudentClassEnum.CANCEL_CLASS);
            LessonStudent lastLesson = lessonStudentService.getLastLessonByEnrollmentId(record.getEnrollmentId());
            if (lastLesson != null) {
                record.setLastLessonClassId(lastLesson.getStudentClassId());
            }
            // 创建退班业绩记录
            sellRecordService.createCancelSellRecord(enrollment, refund);
        } else {
            throw new BusinessException("不允许的操作");
        }
        record.setOperateStatus(SUCCESS);
        enrollmentService.updateEnrollment(enrollment, record);
        log.info("退班退课成功, record: {}", record);
    }

    /**
     * 获取退班退课课次
     * 如果转班，且设置了只转剩余课次，则通过查询获取。否则根据自定义传入课次顺序获取
     */
    private List<LessonStudent> getCancelLessonList(ClassCancelReq req, Enrollment enrollment) {
        if (req.isOnlyRemainLesson() && req.getChangeType() == CANCEL_CLASS) {
            return lessonStudentService.getListByEnrollmentId(enrollment.getId(), true);
        } else {
            return lessonStudentService.getListByLessonIndex(enrollment.getId(), req.getCancelLessonIndexList());
        }
    }

    /**
     * 验证退班退课请求
     */
    private void validateCancelReq(ClassCancelReq req) {
        if (req.getChangeType() == CANCEL_CLASS) {
            // 家长端仅允许退剩余未上课次
            if (req.getOperateSource() == PARENT && !req.isOnlyRemainLesson()) {
                throw new BusinessException("仅允许退剩余未上课次");
            }
            // 自定义课次列表，不能为空
            if (!req.isOnlyRemainLesson() && CollUtil.isEmpty(req.getCancelLessonIndexList())) {
                throw new BusinessException("退课课次不能为空");
            }
        }
        else if (req.getChangeType() == CANCEL_LESSON) {
            // 退课仅限一节
//            ValidationUtils.throwIf(CollUtil.size(req.getCancelLessonIndexList()) != 1, "退课操作仅限1节");
        }
        else {
            throw new BusinessException("操作类型不允许");
        }
        checkCancelLessonIndexList(req);
    }

    /**
     * 检查退课课次列表是否是同一订单项的课次
     * @param req 退课请求
     */
    private void checkCancelLessonIndexList(ClassCancelReq req) {
        //同一学生同一班级不同课次的订单项列表
        List<OrderItem> orderItemList = orderItemService.queryChain().where(ORDER_ITEM.CLASS_ID.eq(req.getClassId())).and(ORDER_ITEM.STUDENT_ID.eq(req.getStudentId())).list();
        //以课次顺序为键,订单项列表为值构建map
        Map<Integer,String> lessonIndexeMap = new HashMap<>();
        for (OrderItem orderItem : orderItemList){
            for (Integer lessonIndex:orderItem.getLessonIndexes()){
                lessonIndexeMap.put(lessonIndex,orderItem.getId());
            }
        }
        //set用于判断不重复订单项id
        Set<String> itemIdSet = new HashSet<>();
        List<Integer> cancelLessonIndexList = req.getCancelLessonIndexList();
        for (Integer cancelLessonIndex :cancelLessonIndexList){
            itemIdSet.add(lessonIndexeMap.get(cancelLessonIndex));
        }
        ValidationUtils.throwIf(itemIdSet.size()>1,"退课课次不属于同一订单项");

    }

    public Page<ClassCancelResp> getClassCancelPage(ClassCancelQuery query, PageQuery pageQuery) {
        QueryWrapper queryWrapper = QueryWrapper.create()
                .select(
                        ArrayUtil.addAll(ENROLL_CHANGE_RECORD.DEFAULT_COLUMNS, STUDENT.DEFAULT_COLUMNS),
                        CLASS_INFO.NAME.as(ClassCancelResp::getOriginalClassName),
                        CLASS_INFO.LEAD_TEACHER_ID.as(ClassCancelResp::getOriginalLeadTeacherId),
                        CLASS_INFO.CAMPUS_ID.as(ClassCancelResp::getCampusId)
                )
                .from(ENROLL_CHANGE_RECORD)
                    .leftJoin(STUDENT).on(STUDENT.ID.eq(ENROLL_CHANGE_RECORD.STUDENT_ID))
                    .leftJoin(CLASS_INFO).on(CLASS_INFO.ID.eq(ENROLL_CHANGE_RECORD.ORIGINAL_CLASS_ID))
                .where(ENROLL_CHANGE_RECORD.STUDENT_ID.eq(query.getStudentId()))
                    .and(ENROLL_CHANGE_RECORD.ORIGINAL_CLASS_ID.eq(query.getClassId()))
                    .and(ENROLL_CHANGE_RECORD.CHANGE_TYPE.eq(query.getChangeType()))
                    .and(ENROLL_CHANGE_RECORD.OPERATE_SOURCE.eq(query.getOperateSource()))
                    .and(ENROLL_CHANGE_RECORD.OPERATE_STATUS.eq(query.getOperateStatus()))
                    .and(ENROLL_CHANGE_RECORD.CREATE_TIME.ge(query.getCreateDateFrom()))
                    .and(ENROLL_CHANGE_RECORD.CREATE_TIME.lt(query.getCreateDateTo()))
                    .and(ClassInfoService.buildCommonClassQueryCondition(query))
                .orderBy(ENROLL_CHANGE_RECORD.ID.desc());
        return enrollChangeRecordService.getMapper().paginateWithRelationsAs(pageQuery.toPage(), queryWrapper, ClassCancelResp.class);
    }

    public ClassCancelDetailResp getClassCancelDetail(String recordId) {
        QueryWrapper queryWrapper = QueryWrapper.create()
                .select(
                        ArrayUtil.addAll(ENROLL_CHANGE_RECORD.DEFAULT_COLUMNS, STUDENT.DEFAULT_COLUMNS),
                        CLASS_INFO.NAME.as(ClassCancelResp::getOriginalClassName),
                        CLASS_INFO.LEAD_TEACHER_ID.as(ClassCancelResp::getOriginalLeadTeacherId)
                )
                .from(ENROLL_CHANGE_RECORD)
                    .leftJoin(STUDENT).on(STUDENT.ID.eq(ENROLL_CHANGE_RECORD.STUDENT_ID))
                    .leftJoin(CLASS_INFO).on(CLASS_INFO.ID.eq(ENROLL_CHANGE_RECORD.ORIGINAL_CLASS_ID))
                .where(ENROLL_CHANGE_RECORD.ID.eq(recordId));
        ClassCancelDetailResp detail = enrollChangeRecordService.getMapper().selectOneWithRelationsByQueryAs(queryWrapper, ClassCancelDetailResp.class);
        ValidationUtils.throwIfNull(detail,"记录不存在");
        //根据课次顺序查询课次信息
        List<Lesson> originalLesson = lessonService.getLessonByIndex(detail.getOriginalClassId(), detail.getOriginalLessonIndexes());
        detail.setOriginalLesson(originalLesson);

        ValidationUtils.throwIfNull(detail.getRefund(), "退款记录不存在");
        Order order = orderMapper.selectOneById(detail.getRefund().getOrderId());
        FinAccount finAccount = finAccountMapper.selectOneById(order.getFinAccountId());
        detail.setOrder(order);
        detail.setOrderFinAccount(finAccount);
        return detail;
    }
}
